﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScriptCoreLib;
using ScriptCoreLib.Shared.Avalon.Extensions;
using System.Windows.Controls;
using System.Collections;
using System.Windows;
using System.Windows.Media;
using System.ComponentModel;
using ScriptCoreLib.Shared.Lambda;

namespace AvalonTycoonMansion.Shared.Isometric
{
    [Script]
    public class IsometricContainerTile : ISupportsContainer
    {
        public const int TileWidth = 64;
        public const int TileHeight = 32;

        public Canvas Container { get; set; }

        public int X;
        public int Y;

        public readonly IsometricContainer Context;



        public event Action BuildingChanged;
        IsometricContainer.Sprite InternalBuilding;
        public IsometricContainer.Sprite Building
        {
            get
            {
                return InternalBuilding;
            }
            set
            {
                InternalBuilding = value;
                if (BuildingChanged != null)
                    BuildingChanged();

            }
        }

        int InternalAltitude = 0;
        public int Altitude
        {
            get
            {
                return InternalAltitude;
            }
            set
            {
                InternalAltitude = value;

                this.Container.MoveTo(
                    InternalPositionX,
                    InternalPositionY - Altitude
                );

                Panel.SetZIndex(this.Container,
                    InternalPositionY + Context.Height
                    + Altitude

                    );
            }
        }

        int InternalPositionX;
        int InternalPositionY;

        public IsometricContainerTile(IsometricContainer Context, int x, int y)
        {
            this.Context = Context;
            this.X = x;
            this.Y = y;



            var p = Context.ToIsometricPosition(x, y);



            InternalPositionX = Convert.ToInt32(p.X);
            InternalPositionY = Convert.ToInt32(p.Y) - IsometricContainerTile.TileHeight / 2;

            this.Container = new Canvas { Width = TileWidth, Height = TileHeight };

            this.Container.AttachTo(Context.Content);

            Altitude = 0;


        }

        Image RecentTileName;
        internal AvalonTycoonMansion.Shared.Isometric.IsometricContainer.TileDescription InternalDescription;

        public AvalonTycoonMansion.Shared.Isometric.IsometricContainer.TileDescription Description
        {
            get
            {
                return InternalDescription;
            }
            set
            {
                if (value == InternalDescription)
                {
                    this.Age = 0;
                    // fake it
                    if (Cleared != null)
                        Cleared();

                    return;
                }

                Clear();
                InternalDescription = value;
                if (value == null)
                {
                    return;
                }

                IsometricContainer.TileDescription Foundation = null;

                if (value.Foundation != null)
                    Foundation = value.Foundation();

                for (int i = 0; i < value.SizeX; i++)
                    for (int j = 0; j < value.SizeY; j++)
                    {
                        var k = this.Context[this.X + i, this.Y - j];

                        if (k != null)
                            if (k != this)
                            {
                                k.Description = Foundation;
                                k.Altitude = this.Altitude;
                            }
                    }

                if (Foundation == null)
                {
                    Foundation = value;
                }
                else
                {
                    if (value.Source != null)
                    {
                        this.Building = value.ToSprite(Context);
                        this.Building.MoveTo(this.X + (this.Building.Description.SizeX * 0.5), this.Y - (this.Building.Description.SizeY * 0.5));
                    }
                    //this.AddTo(Context.Buildings);
                }

                RecentTileName = Foundation.ToImage().MoveTo(0, TileHeight - Foundation.ImageHeight + (Foundation.SizeY - 1) * IsometricContainerTile.TileHeight / 2 - Foundation.Altitude).AttachTo(this.Container);
            }
        }

        public event Action Cleared;
        public void Clear()
        {
            if (Building != null)
            {
                Building.OrphanizeContainer(); ;
                //this.RemoveFrom(Context.Buildings);
                Building = null;
            }

            if (RecentTileName != null)
            {
                RecentTileName.Orphanize();
                RecentTileName = null;
            }

            InternalDescription = null;
            Age = 0;

            if (Cleared != null)
                Cleared();
        }

        public int Age;

        public int FuzzyAge
        {
            get
            {
                return Age - (Y % 3 - X % 3);
            }
        }

        public IsometricContainerTile this[int offsetx, int offsety]
        {
            get
            {
                return this.Context[this.X + offsetx, this.Y + offsety];
            }
        }
    }


    [Script]
    public class IsometricContainer : ISupportsContainer, IEnumerable<IsometricContainerTile>
    {
        public Canvas Container { get; set; }

        [Script]
        public class TileDescription
        {
            public int SizeX = 1;
            public int SizeY = 1;

            public int ImageWidth = IsometricContainerTile.TileWidth;
            public int ImageHeight = IsometricContainerTile.TileHeight;

            public int Altitude;

            public int AnimationDelay;
            public Func<TileDescription> AnimationNextFrame;

            string InternalSourceName;
            public string SourceName
            {
                get
                {
                    return InternalSourceName;
                }
                set
                {
                    if (value == null)
                        return;

                    InternalSourceName = value;
                    Source = (KnownAssets.Path.Assets + "/" + value + ".png").ToSource();
                }
            }

            public ImageSource Source;

            public Func<TileDescription> Foundation;
            public Func<TileDescription> Deconstructor;

            public static implicit operator TileDescription(string e)
            {
                return new TileDescription { SourceName = e };
            }

            public static implicit operator TileDescription(int e)
            {
                return "" + e;
            }

            public Image ToImage()
            {
                return
                    new Image
                    {
                        Source = this.Source,
                        Width = this.ImageWidth,
                        Height = this.ImageHeight
                    };
            }

            public Sprite ToSprite(IsometricContainer Context)
            {
                var s = new IsometricContainer.Sprite(Context, this.ImageWidth, this.ImageHeight)
                {
                    Altitude = -this.SizeX * IsometricContainerTile.TileHeight / 2,
                    Description = this
                };

                this.ToImage().AttachTo(s);

                return s;
            }
        }

        //public readonly BindingList<Tile> Buildings = new BindingList<Tile>();

   
        [Script]
        public class Sprite : ISupportsContainer
        {
            public int Width;
            public int Height;

            public Canvas Container { get; set; }


            public readonly IsometricContainer Context;

            public TileDescription Description;

            public Sprite(IsometricContainer Context, int Width, int Height)
            {
                this.Width = Width;
                this.Height = Height;
                this.Context = Context;
                this.Container = new Canvas
                {
                    Width = Width,
                    Height = Height,

                    //Background = Brushes.Red,

                    Visibility = Visibility.Hidden
                };

                this.Container.AttachTo(Context.Content);
            }

            bool InternalIsOverlay;
            public bool IsOverlay
            {
                get
                {
                    return InternalIsOverlay;
                }
                set
                {
                    InternalIsOverlay = value;

                    this.Container.Orphanize();

                    if (value)
                        this.Container.AttachTo(this.Context.Overlay);
                    else
                        this.Container.AttachTo(this.Context.Content);
                }
            }



            bool InternalMoveToFlag;

            public void MoveTo(IsometricContainerTile t)
            {
                MoveTo(
                    t.X + 0.5 * t.Description.SizeX,
                    t.Y - 0.5 * t.Description.SizeY
                );
            }

            int InternalAltitude;
            public int Altitude
            {
                get
                {
                    return InternalAltitude;
                }
                set
                {
                    InternalAltitude = value;

                    if (InternalMoveToFlag)
                        MoveTo(X, Y);
                }
            }

            public double X;
            public double Y;

            public bool IgnoreZIndex;

            public void MoveTo(double x, double y)
            {

                this.X = x;
                this.Y = y;

                var t = this.Context[x, y];

                var p = this.Context.ToIsometricPosition(x, y);

                //var px = Math.Round(p.X / 2) * 2;
                var px = Math.Floor(p.X);
                var py = Math.Floor(p.Y);



                //var py = Math.Round(p.Y);

                this.Container.MoveTo(px - this.Width / 2, py - this.Height - Altitude);

                if (!IgnoreZIndex)
                    Panel.SetZIndex(this.Container,
                        Convert.ToInt32(p.Y) + this.Context.Height + t.Altitude
                        // how does this help us??
                            * 2
                        //+ IsometricContainer.Tile.TileHeight / 2 - 1
                        );

                if (!InternalMoveToFlag)
                {
                    InternalMoveToFlag = true;
                    this.Container.Show();
                }

            }
        }

        internal readonly IsometricContainerTile[] Tiles;

        public readonly int SizeX;
        public readonly int SizeY;

        public readonly int Width;
        public readonly int Height;

        public readonly Canvas Content;
        public readonly Canvas Overlay;

        public IsometricContainer(int width, int height, int cx, int cy, bool ClipToBounds = true)
        {
            this.Width = width;
            this.Height = height;
            this.SizeX = cx;
            this.SizeY = cy;

            Container = new Canvas { Width = width, Height = height, ClipToBounds = ClipToBounds };
            Content = new Canvas { Width = width, Height = height }.AttachTo(Container);
            Overlay = new Canvas { Width = width, Height = height }.AttachTo(Container);


            Panel.SetZIndex(Content, 1);
            Panel.SetZIndex(Overlay, 2);


            var cxRange = Enumerable.Range(0, cx);
            var cxyRange = cxRange.SelectMany(
                x =>
                {
                    return Enumerable.Range(0, cy).Select(
                        y => 
                        {
                            return new { x, y };
                        }
                    );
                }
            ).ToArray();

            var CountCheck = cx * cy;

            if (cxyRange.Length != CountCheck)
                System.Diagnostics.Debugger.Break();

            this.Tiles = Enumerable.ToArray(
                from p in cxyRange
                select new IsometricContainerTile(this, p.x, p.y)
            );

   
        }

        public void Add(string value)
        {

            IncrementPosition();
        }

        public void Add(int value)
        {

            IncrementPosition();
        }

        private void IncrementPosition()
        {
            Position = (Position + 1) % Tiles.Length;
        }

        public int Position { get; set; }

        public IsometricContainerTile this[double x, double y]
        {
            get
            {
                return this[Convert.ToInt32(Math.Floor(x)), Convert.ToInt32(Math.Ceiling(y))];
            }
        }

        public IsometricContainerTile this[int x, int y]
        {
            get
            {
                if (this.Tiles == null)
                    return null;

                //return this.Tiles[x + y * SizeX];
                var value = this.Tiles[y + x * SizeY];

                if (value == null)
                    System.Diagnostics.Debugger.Break();

                return value;
            }
        }

        #region IEnumerable<Tile> Members

        public IEnumerator<IsometricContainerTile> GetEnumerator()
        {
            return this.Tiles.AsEnumerable().GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        public Point ToIsometricPosition(double x, double y)
        {
            var Context = this;

            double ox = (Context.Width - IsometricContainerTile.TileWidth) / 2;
            double oy = (Context.Height - IsometricContainerTile.TileHeight * Context.SizeX) / 2;

            //ox += x * 64;
            //oy += y * 32;

            var r = new Vector(x, y).GetRotation() + 45.DegreesToRadians();
            var d = new Vector(x, y).Length * IsometricContainerTile.TileHeight * Math.Sqrt(2d);

            var px = Math.Round(Math.Cos(r) * d);
            var py = Math.Round(Math.Sin(r) * d * 0.5);

            ox += px;
            oy += py;

            #region Altitude

            if (this.Contains(x, y))
                if (this[x, y] != null)
                    oy -= this[x, y].Altitude;
            #endregion

            return new Point { X = ox, Y = oy + IsometricContainerTile.TileHeight / 2 };
        }

        public Point FromIsometricPosition(double x, double y)
        {
            //var cx = Tile.TileWidth * this.SizeX / 2;
            //var cy = Tile.TileHeight * this.SizeY / 2;

            //var offset = new Vector(cx - x, (cy - y) / 0.5);
            var a = (IsometricContainerTile.TileHeight * SizeX - Height) / 2;
            var offset = new Vector(
                x - Width / 2 + IsometricContainerTile.TileWidth / 2,

                //(y + Height / 2 - Tile.TileHeight / 2) / 0.5

                // 25x25 tiles
                // 200 => 300
                // 400 => 200
                // 600 => 100
                // 800 => 0
                // 1000 => -100
                (y + a - IsometricContainerTile.TileHeight / 2) / 0.5

                );

            var d = offset.Length / (IsometricContainerTile.TileHeight * Math.Sqrt(2d));
            var r = offset.GetRotation() - 45.DegreesToRadians();

            var nx = Math.Cos(r) * d;
            var ny = Math.Sin(r) * d;

            return new Point
            {
                X = nx,
                Y = ny
            };
        }

        public Point FromIsometricPositionAtAltitude(double x, double y)
        {
            var p = FromIsometricPosition(x, y);

            if (this.Contains(p.X, p.Y))
            {
                var t = this[p.X, p.Y];


                p.X += Convert.ToDouble(t.Altitude) / (IsometricContainerTile.TileHeight);
                p.Y += Convert.ToDouble(t.Altitude) / (IsometricContainerTile.TileHeight);
            }

            return p;
        }

        public bool Contains(double ix, double iy)
        {
            if (ix < 0)
                return false;

            if (iy < 0)
                return false;

            if (ix >= this.SizeX)
                return false;

            if (iy >= this.SizeY)
                return false;

            return true;
        }

        public void Fill(TileDescription value)
        {
            foreach (var t in this.Tiles)
            {
                t.Description = value;
            }
        }
    }
}
